/* **********************************************************
 * Copyright 2010 Dimitar Vangelovski
 *
 * This file is part of the Gens3 3D Engine.
 * 
 * The Gens3 3D Engine is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU General Public
 * License as published by the Free Software Foundation.
 * 
 * The Gens3 3D Engine is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with the Gens3 3D Engine; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA
 * **********************************************************/


#include "common.h"
#include "DrawGL.h"
#include "App.h"
#include "ImageGL.h"
#include "LoaderASE.h"
#include "Lighting.h"
#include "3DMath.h"
#include "MeshFaceNormal.h"
#include "MeshFace.h"
#include "MeshTface.h"
#include "TextureUVW.h"
#include "Material.h"
#include "SubMaterial.h"
#include "MapDiffuse.h"
#include "GeomObject.h"
#include "LightObject.h"
#include "Mesh.h"
#include "MeshTVertList.h"
#include "MeshTFaceList.h"
#include "Scene.h"
#include "AnimObject.h"

#define	MAX_TEXTURES 10000

extern unsigned int	g_iTexture[MAX_TEXTURES];

CDrawGL::CDrawGL()
{
}

CDrawGL::~CDrawGL()
{
	m_TrData.clear();
}

bool CDrawGL::InitializeGL(CScene * pScene)
{
	// glEnable(GL_POLYGON_SMOOTH);
	// glEnable(GL_MULTISAMPLE);
	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);				// Black Background
	glClearDepth(1.0f);						// Depth Buffer Setup
	glDepthFunc(GL_LEQUAL);						// Type Of Depth Testing
	glEnable(GL_DEPTH_TEST);					// Enable Depth Testing	
	glDisable(GL_BLEND);
	glEnable(GL_TEXTURE_2D);					// Enable Texture Mapping
	glDepthMask(GL_TRUE);
	glEnable(GL_CULL_FACE);						// Remove Back Face
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
	glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
	glHint(GL_FOG_HINT, GL_NICEST);

	// Setup fog
	//GLfloat fogColor[4] = {0.0, 0.25, 0.25, 1.0};
	//glFogi (GL_FOG_MODE, GL_EXP2);
	//glFogfv (GL_FOG_COLOR, fogColor);
	//glFogf (GL_FOG_DENSITY, 0.03f);
	//glFogf (GL_FOG_START, 5.0f);
	//glFogf (GL_FOG_END, 100.0f);
	//glEnable(GL_FOG);

	// Initialize transparent polygons data
	TrInit(pScene);

	// Adjust and activate all the lights in the world (<= 8)
	SetupLights(pScene);
	glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);		// Enable lighting relative to the camera (viewer) - more realisic effect when using specular highlights
	glEnable(GL_LIGHTING);						// Enable Light Processing Harware

	// GLenum err = glGetError();

	return true;
}

void CDrawGL::DeinitializeGL()
{
	glDeleteTextures(MAX_TEXTURES, &g_iTexture[0]);
}

void CDrawGL::DrawNonTransparentSurfaces(CScene * pScene)
{
	int iMatRef;
	unsigned int iNewSubMatIndex, iOldSubMatIndex;
	unsigned int iNewMatIndex, iOldMatIndex;
	int iVertIndex0,iVertIndex1,iVertIndex2;
	int iTVertIndex0,iTVertIndex1,iTVertIndex2;
	bool bHasSubMaterial, bTransparentObject, bTransparentFace;

	CGeomObject *		pGeomObject;
	CMesh *			pMesh;
	CMapDiffuse *		pMapDiffuse;
	CMeshFaceNormal		meshFaceNormal;
	CMeshVertexNormal	vertexNormal0,vertexNormal1,vertexNormal2;
	CMeshTface		meshTFace;
	CTextureUVW		meshTVert0, meshTVert1, meshTVert2;
	CMeshFace		meshFace;
	CVertex			meshVertex0, meshVertex1, meshVertex2;


	iOldMatIndex = 0xFFFFFFFF;
	iNewMatIndex = 0xFFFFFFFF;
	iOldSubMatIndex = 0xFFFFFFFF;
	iNewSubMatIndex = 0xFFFFFFFF;

	glBegin(GL_TRIANGLES);

	// go through all the objects in the world
	for (int iGeomObj = 0; iGeomObj < pScene->GetGeomObjectCount(); iGeomObj++)
	{
		pGeomObject = pScene->GetGeomObject(iGeomObj);
		pMesh = pGeomObject->GetMesh();
		
		bHasSubMaterial = false;
		bTransparentObject = false;
		iMatRef = pGeomObject->GetMaterialRef();
		
		// if material is not applied to this object => set the default null texture
		if (iMatRef == -1) 
		{
			iNewMatIndex = g_iTexture[0];

			if (iNewMatIndex != iOldMatIndex) // the material is different from the previous frame => change it
			{
				glEnd();
				glBindTexture(GL_TEXTURE_2D, iNewMatIndex);
				SetupMaterial_NoName();
				glBegin(GL_TRIANGLES);
			}
		} 
		else 
		{
			// check if this material has submaterials
			if (pScene->GetMaterial(iMatRef)->GetNumSubMtls() > 0)
			{
				bHasSubMaterial = true;
			}
			else
			{
				// if not, assign the material
				pMapDiffuse =  pScene->GetMaterial(iMatRef)->GetMapDiffuse();
				iNewMatIndex = g_iTexture[pMapDiffuse->GetTextureIndex()];
				bTransparentObject = pMapDiffuse->IsTransparent();

				if (!bTransparentObject)
				{
					if (iNewMatIndex != iOldMatIndex) // the material is different from the previous frame => change it
					{
						glEnd();
						glBindTexture(GL_TEXTURE_2D, iNewMatIndex);
						SetupMaterial(iMatRef, pScene); // setup material properties
						glBegin(GL_TRIANGLES);
					}
				}
			}
		}
		
		iOldSubMatIndex = iNewMatIndex;
		iNewSubMatIndex = iOldSubMatIndex;		
	
		if (!bTransparentObject) 
		{
			// go through all faces of this object
			for (int iMeshFace = 0; iMeshFace < pMesh->GetMeshNumFaces(); iMeshFace++)
			{
				bTransparentFace = false;
				meshFace = pMesh->GetMeshFace(iMeshFace);

				// assign the submaterial to this face
				if (bHasSubMaterial) 
				{
					pMapDiffuse = pScene->GetMaterial(iMatRef)->GetSubMaterial(meshFace.GetMeshMtlID())->GetMapDiffuse();
					iNewSubMatIndex = g_iTexture[pMapDiffuse->GetTextureIndex()];
					bTransparentFace = pMapDiffuse->IsTransparent();

					if (!bTransparentFace)
					{
						if (iNewSubMatIndex != iOldSubMatIndex) // the material is different from the previous frame => change it
						{
							glEnd();
							glBindTexture(GL_TEXTURE_2D, iNewSubMatIndex);	
							SetupSubMaterial(iMatRef, meshFace.GetMeshMtlID(), pScene); // setup submaterial properties
							glBegin(GL_TRIANGLES);
						}
					}
				}

				// draw the non-transparent face
				if (!bTransparentFace)
				{
					iVertIndex0 = meshFace.GetVertexIndex(0);
					iVertIndex1 = meshFace.GetVertexIndex(1);
					iVertIndex2 = meshFace.GetVertexIndex(2);

					meshTFace = pMesh->GetMeshTFaceList()->GetMeshTFace(iMeshFace);
					iTVertIndex0 = meshTFace.GetTVertIndex(0);
					iTVertIndex1 = meshTFace.GetTVertIndex(1);
					iTVertIndex2 = meshTFace.GetTVertIndex(2);

					meshTVert0 = pMesh->GetMeshTVertList()->GetMeshTVert(iTVertIndex0);
					meshTVert1 = pMesh->GetMeshTVertList()->GetMeshTVert(iTVertIndex1);
					meshTVert2 = pMesh->GetMeshTVertList()->GetMeshTVert(iTVertIndex2);

					meshVertex0 = pMesh->GetVertex(iVertIndex0);
					meshVertex1 = pMesh->GetVertex(iVertIndex1);
					meshVertex2 = pMesh->GetVertex(iVertIndex2);

					//// Precalculated Normals (from 3D Studio)
					//meshFaceNormal = pMesh->GetFaceNormal(iMeshFace);
					//vertexNormal0 = meshFaceNormal.GetVertNormal(0);
					//vertexNormal1 = meshFaceNormal.GetVertNormal(1);
					//vertexNormal2 = meshFaceNormal.GetVertNormal(2);

					vertexNormal0 = CalculateNormal(meshVertex0, meshVertex1, meshVertex2);
					vertexNormal1 = CalculateNormal(meshVertex1, meshVertex2, meshVertex0);
					vertexNormal2 = CalculateNormal(meshVertex2, meshVertex0, meshVertex1);

					glNormal3f(vertexNormal0.x, vertexNormal0.y, vertexNormal0.z);
					glTexCoord2f(meshTVert0.u, meshTVert0.v);
					glVertex3f(meshVertex0.x, meshVertex0.y, meshVertex0.z);

					glNormal3f(vertexNormal1.x, vertexNormal1.y, vertexNormal1.z);
					glTexCoord2f(meshTVert1.u, meshTVert1.v);
					glVertex3f(meshVertex1.x, meshVertex1.y, meshVertex1.z);

					glNormal3f(vertexNormal2.x, vertexNormal2.y, vertexNormal2.z);
					glTexCoord2f(meshTVert2.u, meshTVert2.v);
					glVertex3f(meshVertex2.x, meshVertex2.y, meshVertex2.z);
				}
					
				iOldSubMatIndex = iNewSubMatIndex;
			}
				
			iOldMatIndex = iOldSubMatIndex;
		}
	}

	glEnd();
}

void CDrawGL::DrawTransparentSurfaces(CScene * pScene)
{
	int iMatRef;
	unsigned int iNewMatIndex, iOldMatIndex;
	int iVertIndex0,iVertIndex1,iVertIndex2;
	int iTVertIndex0,iTVertIndex1,iTVertIndex2;

	CGeomObject *		pGeomObject;
	CMesh *			pMesh;
	CMapDiffuse *		pMapDiffuse;
	CMeshFaceNormal		meshFaceNormal;
	CMeshVertexNormal	vertexNormal0,vertexNormal1,vertexNormal2;
	CMeshTface		meshTFace;
	CTextureUVW		meshTVert0, meshTVert1, meshTVert2;
	CMeshFace		meshFace;
	CVertex			meshVertex0, meshVertex1, meshVertex2;

	int n = m_TrData.size();
	if (n < 1) return;	// if no transparent polygons => exit

	glDisable(GL_LIGHTING);
	glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
	glEnable(GL_LIGHTING);
	glDisable(GL_CULL_FACE);
	glDepthMask(0);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_BLEND);	

	// Transform and sort the transparent polygons
	// for less than 2 polygons transform and sort is not needed
	if (n > 1)
	{
		TrTransform();
		TrQSort(0, n - 1);
	}

	iOldMatIndex = 0xFFFFFFFF;
	iNewMatIndex = 0xFFFFFFFF;

	glBegin(GL_TRIANGLES);

	// Draw all the transparent polygons back-to-front
	for (int i = 0; i < n; i++)
	{
		pGeomObject = pScene->GetGeomObject(m_TrData[i].iGeomIndex);
		pMesh = pGeomObject->GetMesh();
		iMatRef = pGeomObject->GetMaterialRef();
		meshFace = pMesh->GetMeshFace(m_TrData[i].iFaceIndex);
			
		if (pScene->GetMaterial(iMatRef)->GetNumSubMtls() == 0) // if no submaterials
		{
			// assign the material
			pMapDiffuse = pScene->GetMaterial(iMatRef)->GetMapDiffuse();
			iNewMatIndex = g_iTexture[pMapDiffuse->GetTextureIndex()];

			if (iNewMatIndex != iOldMatIndex) // the material is different from the previous frame => change it
			{
				glEnd();
				glBindTexture(GL_TEXTURE_2D, iNewMatIndex);
				SetupMaterialAlpha(iMatRef, pScene); // setup transparent material properties
				glBegin(GL_TRIANGLES);
			}
		} 
		else 
		{
			// assign the submaterial
			pMapDiffuse = pScene->GetMaterial(iMatRef)->GetSubMaterial(meshFace.GetMeshMtlID())->GetMapDiffuse();
			iNewMatIndex = g_iTexture[pMapDiffuse->GetTextureIndex()];
			
			if (iNewMatIndex != iOldMatIndex) // the material is different from the previous frame => change it
			{
				glEnd();
				glBindTexture(GL_TEXTURE_2D, iNewMatIndex);	
				SetupSubMaterialAlpha(iMatRef, meshFace.GetMeshMtlID(), pScene); // setup transparent submaterial properties
				glBegin(GL_TRIANGLES);
			}
		}

		/////////////////////////////////////////////////////////////////////////////////////////
		// draw the transparent face

		iVertIndex0 = meshFace.GetVertexIndex(0);
		iVertIndex1 = meshFace.GetVertexIndex(1);
		iVertIndex2 = meshFace.GetVertexIndex(2);
		
		meshTFace = pMesh->GetMeshTFaceList()->GetMeshTFace(m_TrData[i].iFaceIndex);
		iTVertIndex0 = meshTFace.GetTVertIndex(0);
		iTVertIndex1 = meshTFace.GetTVertIndex(1);
		iTVertIndex2 = meshTFace.GetTVertIndex(2);

		meshTVert0 = pMesh->GetMeshTVertList()->GetMeshTVert(iTVertIndex0);
		meshTVert1 = pMesh->GetMeshTVertList()->GetMeshTVert(iTVertIndex1);
		meshTVert2 = pMesh->GetMeshTVertList()->GetMeshTVert(iTVertIndex2);

		meshVertex0 = pMesh->GetVertex(iVertIndex0);
		meshVertex1 = pMesh->GetVertex(iVertIndex1);
		meshVertex2 = pMesh->GetVertex(iVertIndex2);

		//// Precalculated Normals (from 3D Studio)
		//meshFaceNormal = pMesh->GetFaceNormal(m_TrData[i].iFaceIndex);
		//vertexNormal0 = meshFaceNormal.GetVertNormal(0);
		//vertexNormal1 = meshFaceNormal.GetVertNormal(1);
		//vertexNormal2 = meshFaceNormal.GetVertNormal(2);

		vertexNormal0 = CalculateNormal(meshVertex0, meshVertex1, meshVertex2);
		vertexNormal1 = CalculateNormal(meshVertex1, meshVertex2, meshVertex0);
		vertexNormal2 = CalculateNormal(meshVertex2, meshVertex0, meshVertex1);

		glNormal3f(vertexNormal0.x, vertexNormal0.y, vertexNormal0.z);
		glTexCoord2f(meshTVert0.u, meshTVert0.v);
		glVertex3f(meshVertex0.x, meshVertex0.y, meshVertex0.z);

		glNormal3f(vertexNormal1.x, vertexNormal1.y, vertexNormal1.z);
		glTexCoord2f(meshTVert1.u, meshTVert1.v);
		glVertex3f(meshVertex1.x, meshVertex1.y, meshVertex1.z);

		glNormal3f(vertexNormal2.x, vertexNormal2.y, vertexNormal2.z);
		glTexCoord2f(meshTVert2.u, meshTVert2.v);
		glVertex3f(meshVertex2.x, meshVertex2.y, meshVertex2.z);

		iOldMatIndex = iNewMatIndex;
	}

	glEnd();

	glDepthMask(GL_TRUE);
	glEnable(GL_CULL_FACE);
	glDisable(GL_BLEND);
	//glDisable(GL_LIGHTING);
	//glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_true);
	//glEnable(GL_LIGHTING);	
}

void CDrawGL::AnimateObjects(CScene * pScene)
{
	// animate the animation objects
	for (unsigned int i = 0; i < pScene->GetAnimObjects().size(); i++)
	{
		CAnimObject * pAnimObject = pScene->GetAnimObjects()[i];

		if (pAnimObject->GetObjectType() == CAnimObject::eGeomObject)
		{
			CGeomObject * pGeomObject = pScene->GetGeomObject(pAnimObject->GetSceneObjIndex());
			CMesh * pMesh = pGeomObject->GetMesh();
			pMesh->SetVertexList(pAnimObject->Animate(1.0f));
		}
		else
		{
			CLightObject * pLightObject = pScene->GetLightObject(pAnimObject->GetSceneObjIndex());
			pLightObject->SetTmPos(pAnimObject->Animate(1.0f)[0]);
		}
	}
}

// Quick sort the transparent data
void CDrawGL::TrQSort(int l, int r)
{
	STrData trTemp;
	int i = l;
	int j = r;
	float z = m_TrData[(l + r) / 2].zv;

	do {
		while (m_TrData[i].zv > z) i++;
		while (z > m_TrData[j].zv) j--;

		if (i <= j) 
		{
			TrSwap(i, j);
			i++;
			j--;
		}
	} while (i <= j);

	if (l < j) TrQSort(l, j);
	if (i < r) TrQSort(i, r);
}

// transform the transparent data
void CDrawGL::TrTransform()
{
	float m[16];
	float v1[4];
	float v2[4];

	glGetFloatv(GL_MODELVIEW_MATRIX, (float *)&m[0]);
	
	unsigned int n = m_TrData.size();

	for (unsigned int i = 0; i < n; i++)
	{
		v1[0] = m_TrData[i].x;
		v1[1] = m_TrData[i].y;
		v1[2] = m_TrData[i].z;
		v1[3] = 1.0f;

		VectorMatrixMult(v1, m, v2);

		// insert the calculated polygon z coordinate for sorting
		m_TrData[i].zv = v2[2];

		// calculate the distance from the eye of the view space polygon vertex
		//m_TrData[i].zv = (float)sqrt(v2[0] * v2[0] + v2[1] * v2[1] + v2[2] * v2[2]);
	}
}

// setup transparent polygons for sorting
void CDrawGL::TrInit(CScene *pScene)
{
	int iMatRef;
	CMesh * pMesh;
	CMeshFace meshFace;

	int iVInd0, iVInd1, iVInd2;
	CVertex v0, v1, v2;
	STrData trData;

	for (int iGeomObj = 0; iGeomObj < pScene->GetGeomObjectCount(); iGeomObj++)
	{
		pMesh = pScene->GetGeomObject(iGeomObj)->GetMesh();
		iMatRef = pScene->GetGeomObject(iGeomObj)->GetMaterialRef();
		
		if (iMatRef != -1) // has material
		{
			if (pScene->GetMaterial(iMatRef)->GetNumSubMtls() == 0)
			{
				// Go through all materials
				if (pScene->GetMaterial(iMatRef)->GetMapDiffuse()->IsTransparent())
				{
					for (int iMeshFace = 0; iMeshFace < pMesh->GetMeshNumFaces(); iMeshFace++)
					{
						meshFace = pMesh->GetMeshFace(iMeshFace);
						iVInd0 = meshFace.GetVertexIndex(0);
						iVInd1 = meshFace.GetVertexIndex(1);
						iVInd2 = meshFace.GetVertexIndex(2);
						v0 = pMesh->GetVertex(iVInd0);
						v1 = pMesh->GetVertex(iVInd1);
						v2 = pMesh->GetVertex(iVInd2);

						// Calculate an average vertex for the polygon from all the polygon vertices
						trData.x = (((v0.x + v1.x) / 2.0f) + v2.x) / 2.0f;
						trData.y = (((v0.y + v1.y) / 2.0f) + v2.y) / 2.0f;
						trData.z = (((v0.z + v1.z) / 2.0f) + v2.z) / 2.0f;
						trData.iFaceIndex = iMeshFace;
						trData.iGeomIndex = iGeomObj;

						m_TrData.push_back(trData);
					}
				}
			} 
			else
			{
				// Go through all submaterials
				for (int iMeshFace = 0; iMeshFace < pMesh->GetMeshNumFaces(); iMeshFace++)
				{
					meshFace = pMesh->GetMeshFace(iMeshFace);

					if (pScene->GetMaterial(iMatRef)->GetSubMaterial(meshFace.GetMeshMtlID())->GetMapDiffuse()->IsTransparent())
					{
						iVInd0 = meshFace.GetVertexIndex(0);
						iVInd1 = meshFace.GetVertexIndex(1);
						iVInd2 = meshFace.GetVertexIndex(2);
						v0 = pMesh->GetVertex(iVInd0);
						v1 = pMesh->GetVertex(iVInd1);
						v2 = pMesh->GetVertex(iVInd2);

						// Calculate an average vertex for the polygon from all the polygon vertices
						trData.x = (((v0.x + v1.x) / 2.0f) + v2.x) / 2.0f;
						trData.y = (((v0.y + v1.y) / 2.0f) + v2.y) / 2.0f;
						trData.z = (((v0.z + v1.z) / 2.0f) + v2.z) / 2.0f;
						trData.iFaceIndex = iMeshFace;
						trData.iGeomIndex = iGeomObj;

						m_TrData.push_back(trData);
					}
				}
			}
		}
	}
}

